﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Configuration;
using System.Windows;
using System.Security.AccessControl;
using System.Security.Permissions;
using System.Security;
using System.Reflection;
using System.Linq.Expressions;

namespace WpfTraceSolution
{
    public static class LogHelper
    {
        #region private variable
        //{datetime} {MethodInfo|FileInfo} {CustomInfo} 
        const string formatWithMethodInfo = "{0} | {1} | {2}";
        const string formatErrorWithMethodInfo = "Err: {0} | {1} | {2}";
        const string formatWithTime = "{0} | {1} ";
        const string formatTimeWithMilliSecond = "HH:mm:ss fff";

        #endregion

        #region private method
        /// <summary>
        /// get current call method name
        /// </summary>
        /// <returns>FullName.Name,example as [WpfTraceSolution.MainWindow.button1_Click]</returns>
        static string GetExecutingMethodName()
        {
            string result = "Unknown";
            StackTrace trace = new StackTrace(false);

            for (int index = 0; index < trace.FrameCount; ++index)
            {
                StackFrame frame = trace.GetFrame(index);
                MethodBase method = frame.GetMethod();
                Type declaringType = method.DeclaringType;
                if (declaringType != typeof(LogHelper))
                {
                    result = string.Concat(method.DeclaringType.FullName, ".", method.Name);
                    break;
                }
            }

            return result;
        }

        /// <summary>
        /// get Execute details ,output example as [F:\WpfTraceSolution\MainWindow.xaml.cs(79) | WpfTraceSolution.MainWindow.btnWriteWithFileInfo_Click]
        /// </summary>
        /// <returns></returns>
        static string GetExecutingInfo()
        {
            string result = "Unknown";
            StringBuilder sbInfo = new StringBuilder();
            StackTrace trace = new StackTrace(true);

            for (int index = 0; index < trace.FrameCount; ++index)
            {
                StackFrame frame = trace.GetFrame(index);
                MethodBase method = frame.GetMethod();
                Type declaringType = method.DeclaringType;
                if (declaringType != typeof(LogHelper))
                {
                    sbInfo.AppendFormat("{0}", frame.GetFileName());
                    sbInfo.AppendFormat("({0}) ", frame.GetFileLineNumber());
                    sbInfo.AppendFormat("| {0}.{1}", method.DeclaringType.FullName, method.Name);
                    result = sbInfo.ToString();
                    break;
                }
            }

            return result;
        }
        #endregion

        #region public methods
        /// <summary>
        /// init by configuration
        /// </summary>
        public static void Init()
        {
            #region define
            string logPathKey = "logPath", logDestinationKey = "logDestination";
            string logPath = string.Empty, logDestination = string.Empty;
            FileStream stream;
            FileMode fileMode;
            #endregion

            #region get destination
            //no destination,return
            if (!ConfigurationManager.AppSettings.AllKeys.Contains(logDestinationKey)) return;
            logDestination = ConfigurationManager.AppSettings[logDestinationKey].Trim();

            int logType = 0;
            Int32.TryParse(logDestination, out logType);
            #endregion

            #region switch log type
            Trace.AutoFlush = true;
            switch (logType)
            {
                case 0:
                    {
                        //0 - No output
                        Trace.Listeners.Clear();
                        return;
                    }
                case 1:
                    {
                        //1 - Debugger (Such as DbgView)
                        //use default listener
                        return;
                    }
                case 2:
                    {
                        //2 - File (Overwrite the old file and doesn't close it until application exits)
                        Trace.Listeners.Clear();
                        fileMode = FileMode.Create; break;
                    }
                case 3:
                    {
                        //3 - File (Append the log at the end of file and close it after each log output)
                        Trace.Listeners.Clear();
                        fileMode = FileMode.Append; break;
                    }
                case 4:
                    {
                        //4 - Debugger&File (Append the log at the end of file and close it after each log output)
                        fileMode = FileMode.Append; break;
                    }

                default: return;
            }
            #endregion

            #region check path
            //path is null
            logPath = ConfigurationManager.AppSettings[logPathKey].Trim();
            if (string.IsNullOrEmpty(logPath)) return;

            //path has invalid char
            var pathCharArray = logPath.ToCharArray();
            if (pathCharArray.Any(o => Path.GetInvalidPathChars().Contains(o)))
                return;

            //FileName has invalid char
            //note : invalid file name chars count is 41, 
            //invalid path  chars count  is 36
            //and , the top 36 of invalid file name chars  are  same as invalid path chars
            //so,first check path invalid chars,second check filename,only filename
            var filenameCharArray = Path.GetFileName(logPath).ToCharArray();
            if (filenameCharArray.Any(o => Path.GetInvalidFileNameChars().Contains(o)))
                return;

            //EnvironmentVariables Path
            if (logPath.Contains('%'))
                logPath = Environment.ExpandEnvironmentVariables(logPath);

            //cheng relative path to absolute path.
            if (String.IsNullOrEmpty(Path.GetPathRoot(logPath)))
                logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, logPath);
            #endregion

            #region file log
            //risk:directory readonly;need administrator right to createfile;and so on
            //use try-catch
            try
            {
                if (!Directory.Exists(Path.GetDirectoryName(logPath)))
                    Directory.CreateDirectory(Path.GetDirectoryName(logPath));

                stream = File.Open(logPath, fileMode, FileAccess.Write, FileShare.ReadWrite);
                TextWriterTraceListener text = new TextWriterTraceListener(stream);
                //text.TraceOutputOptions = TraceOptions.DateTime;
                Trace.Listeners.Add(text);
            }
            catch (Exception ex)
            {
                Trace.Write(ex);
            }
            #endregion

        }

        /// <summary>
        /// write by format string
        /// </summary>
        /// <param name="format">A format string that contains zero or more format items, which correspond   to objects in the args array.</param>
        /// <param name="args"> An object array containing zero or more objects to format.</param>
        /// <exception >
        /// string.format fail,only output format 
        /// </exception>
        public static void Write(string format, params object[] args)
        {
            try
            {
                Trace.WriteLine(string.Format(format, args));
            }
            catch (Exception ex)
            {
                Trace.Write(ex);
            }
            finally
            {
                Trace.WriteLine(format);
            }
        }

        /// <summary>
        /// example:{2011/8/10 10:10:49 | WpfTraceSolution.MainWindow.btnWriteWithMethod_Click | Do Write With method]
        /// </summary>
        /// <param name="info"></param>
        public static void WriteWithMethod(string info)
        {
            Trace.WriteLine(string.Format(formatWithMethodInfo, DateTime.Now.ToString(), GetExecutingMethodName(), info));
        }

        /// <summary>
        /// example: [2011/8/10 10:11:59 | F:\WpfTraceSolution\MainWindow.xaml.cs(79) | WpfTraceSolution.MainWindow.btnWriteWithFileInfo_Click | Do Write With fileInfo]
        /// must has pdb file,otherse ,no file info
        /// </summary>
        /// <param name="info">custom info</param>
        public static void WriteWithFileInfo(string info)
        {
            Trace.WriteLine(string.Format(formatWithMethodInfo, DateTime.Now.ToString(), GetExecutingInfo(), info));
        }

        /// <summary>
        /// output info with time,the time has milliseccond,but no date.
        /// example as :[11:37:18 560 | Do Write With Time ]
        /// </summary>
        /// <param name="info"></param>
        public static void WriteWithTime(string info)
        {
            Trace.WriteLine(string.Format(formatWithTime, DateTime.Now.ToString(formatTimeWithMilliSecond), info));
        }

        /// <summary>
        /// output info with time,the time has milliseccond,but no date.
        /// receive format params
        /// example as :[11:37:18 560 | Do Write With Time ]
        /// </summary>
        /// <param name="format"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        /// <exception >
        /// string.format fail,only output format 
        /// </exception>
        public static void WriteWithTime(string format, params object[] args)
        {
            try
            {
                WriteWithTime(string.Format(format, args));
            }
            catch (Exception ex)
            {
                Trace.Write(ex);
            }
            finally
            {
                WriteWithTime(format);
            }
        }

        /// <summary>
        /// output info with datetime 
        /// example as :[2011/8/10 11:54:31 | Do Write With DateTime  ]
        /// </summary>
        /// <param name="info"></param>
        public static void WriteWithDateTime(string info)
        {
            Trace.WriteLine(string.Format(formatWithTime, DateTime.Now.ToString(), info));
        }

        /// <summary>
        /// output info with datetime 
        /// receive format params
        /// example as :[2011/8/10 11:54:31 | Do Write With DateTime  ]
        /// </summary>
        /// <param name="format">A format string that contains zero or more format items, which correspond   to objects in the args array.</param>
        /// <param name="args"> An object array containing zero or more objects to format.</param>
        /// <returns></returns>
        /// <exception >
        /// string.format fail,only output format 
        /// </exception>
        public static void WriteWithDateTime(string format, params object[] args)
        {
            try
            {
                WriteWithTime(string.Format(format, args));
            }
            catch (Exception ex)
            {
                Trace.Write(ex);
            }
            finally
            {
                WriteWithTime(format);
            }
        }

        /// <summary>
        /// output datetime and exception.message
        /// </summary>
        /// <param name="ex">current exception.</param>
        /// <param name="caption">custom Caption</param>
        public static void WriteException(Exception ex, string caption)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("{0}    {1}", DateTime.Now.ToString(), caption);
            sb.AppendLine();
            sb.Append(ex.ToString());
            Trace.Write(sb.ToString());
        }

        /// <summary>
        /// example:{2011/8/10 10:10:49 | WpfTraceSolution.MainWindow.btnWriteWithMethod_Click | Do Write With method]
        /// </summary>
        /// <param name="info"></param>
        public static void WriteExceptionWithMethod(string info)
        {
            Trace.WriteLine(string.Format(formatErrorWithMethodInfo, DateTime.Now.ToString(), GetExecutingMethodName(), info));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="number"></param>
        public static void WriteInt(object number)
        {
            string XX = LogHelper.ExampleFunction(() => number);
            Trace.WriteLine(XX);
        }

        public static string ExampleFunction(Expression<Func<object>> function)
        {
            string t = (function.Body as MemberExpression).Member.Name;
            return t;
        }

        public static void WriteObject(Func<object> function)
        {
            var name = function.Method.Name;
            var obj = function.Target;
            

            //var parms = function.Parameters;
            //function.Method.GetMethodBody
            //string name = (function.Body as MemberExpression).Member.Name;
            //var mem = (function.Body as MemberExpression);
            //var x = (function.Body as ConstantExpression).Value;

            //Trace.WriteLine(name + ":");
        }
        #endregion
    }
}
